package newcrunch.typography;

import java.util.*;
import javax.swing.text.BadLocationException;
import javax.swing.text.JTextComponent;
import jsyntaxpane.actions.ActionUtils;
import newcrunch.lexers.JavaTypoLexer;
import newcrunch.lexers.JavaTypoParser;
import static newcrunch.typography.JavaDialogData.*;
import newcrunch.typography.TypoResults.BraceInfo;
import newcrunch.util.LexicalUtility;
import newcrunch.util.Misc;
import org.antlr.runtime.ANTLRStringStream;
import org.antlr.runtime.TokenRewriteStream;

/**
 *
 * @author WL
 */
public class JavaAnalyzer
{   
    public static JavaAnalyzer singleton;
    
    private JavaAnalyzer()
    {
    }
    
    public static JavaAnalyzer getInstance()
    {
        if (singleton == null)
            singleton = new JavaAnalyzer();
        
        return singleton;
    }
    
    /**
     * Analyze the source code and return the TypoResults object
     * @param text
     * @param editor
     * @return
     * @throws org.antlr.runtime.RecognitionException 
     */
    public TypoResults analyzeTokens(String text) throws org.antlr.runtime.RecognitionException
    {
            //set up the objects to prepare for parsing
            ANTLRStringStream ss = new ANTLRStringStream(text);
            JavaTypoLexer lex = new JavaTypoLexer(ss);
            TokenRewriteStream tokens = new TokenRewriteStream(lex);
            JavaTypoParser grammar = new JavaTypoParser(tokens, text);
            
            //call this to start parsing
            grammar.compilationUnit();
            
            return grammar.getResults();
    }
    
    public void analyze(JTextComponent editor, TypoMainTable displayTable) throws 
            org.antlr.runtime.RecognitionException, BadLocationException
    {
        int[] info = new int[2];
        List<TokenPosWithLine> comments = LexicalUtility.getComments(editor);
        List<TokenPosWithLine> blankLineLines = LexicalUtility.getBlankLines(editor.getText(), info);
        
        TypoResults tokenResults = analyzeTokens(editor.getText());
        updateSpaceAfter(tokenResults, editor, displayTable);
        updateSpaceBefore(tokenResults, editor, displayTable);
        updateSpaceBeforeAndAfter(tokenResults, editor, displayTable);
        updateUnary(tokenResults, editor, displayTable);
        updateSingleVariables(tokenResults, editor, displayTable);
        updateIdentifiers(tokenResults, editor, displayTable);
        updateAverageLengths(tokenResults, editor, displayTable);
        updatePercentages(tokenResults, editor, displayTable, blankLineLines, comments, info);
        updateMaximumLength(tokenResults, editor, displayTable);
        updateStatementsPerLine(tokenResults, editor, displayTable);
        updateBraceStyle(tokenResults, editor, displayTable);
        updateBlanks(tokenResults, editor, displayTable, blankLineLines, comments);
        
        //newly added according to suggestion given by Aaron
        updateConstructors(tokenResults, editor, displayTable);
    }
        
    /**
     * Counts the number of constructors and display in the table.
     * @param tokenResults
     * @param editor
     * @param table 
     */
    private void updateConstructors(TypoResults tokenResults, 
            JTextComponent editor, TypoMainTable table)
    {
        TypoDetailsDialog dialog = table.getDialog(TypoMainTable.CONSTRUCTOR_COUNT);
        dialog.reset();
        KeyHighlighter highlighter = new KeyHighlighter(editor);
        dialog.setHighlighter(highlighter);
        
        List<TokenPosWithLine> tokens = tokenResults.getConstructors();
        
        for (TokenPosWithLine tok : tokens)
        {
            dialog.addRow(String.valueOf(tok.startLine));
            KeyHighlighter.addKeyHighlightPos(highlighter, tok.start, tok.end + 1);
        }
        
        table.setValueAt(String.valueOf(tokens.size()), TypoMainTable.CONSTRUCTOR_COUNT, 
                TypoMainTable.RESULTS_COLUMN);
        
        table.setValueAt("-", TypoMainTable.CONSTRUCTOR_COUNT, 
                TypoMainTable.COMMENTS_COLUMN);
        
        table.setValueAt("-", TypoMainTable.CONSTRUCTOR_COUNT, 
                TypoMainTable.SD_COLUMN);
    }
    
    private KeyHighlighter resetDialogsAndCreateHighlighter(JTextComponent editor, TypoMainTable table, int row)
    {
        TypoDetailsDialog dialog = table.getDialog(row);
        dialog.reset();
        KeyHighlighter highlighter = new KeyHighlighter(editor);
        dialog.setHighlighter(highlighter);
        table.setValueAt("-", row, TypoMainTable.RESULTS_COLUMN);
        table.setValueAt("-", row, TypoMainTable.SD_COLUMN);
        dialog.setVisible(false);
        return highlighter;
    }
    
    
    private void setBlankStandard(TypoMainTable table, int row, String result)
    {
        table.setValueAt(JavaStandards.getIdentifierNamingComment(result, 
                (String)table.getValueAt(row, TypoMainTable.STANDARDS_COLUMN)), row, 
                TypoMainTable.COMMENTS_COLUMN);
    }
    
    private void setBlankDialog(TypoMainTable table, 
            JTextComponent editor,
            int row, int blanks, int line)
    {
        TypoDetailsDialog dialog = table.getDialog(row);
        KeyHighlighter highlighter = dialog.getHighlighter();
        
        String blankStr = String.valueOf(blanks);
        String lineStr = String.valueOf(line);
        dialog.addRow(lineStr, blankStr);
        KeyHighlighter.addKeyHighlight(highlighter, editor, "", line, 0);
    }
    
    public void updateBlanks(TypoResults tokenResults, JTextComponent editor,
            TypoMainTable table, List<TokenPosWithLine> blankLines, 
            List<TokenPosWithLine> commentPos)
            throws BadLocationException
    {
        List<TypoResults.TokenTracker> tokens = tokenResults.getTokenRanges();
        
        //appends the blank lines
        for (int i = 0; i < blankLines.size(); i++)
            tokens.add(new TypoResults.TokenTracker(blankLines.get(i).startLine, 
                                                    blankLines.get(i).start, 
                                                    TypoResults.RangeType.BLANK));
        
        //appends the comments
        for (int i = 0; i < commentPos.size(); i++)
        {
            TokenPosWithLine curr = commentPos.get(i);
            tokens.add(new TypoResults.TokenTracker(curr.startLine,
                                                    curr.start,
                                                    TypoResults.RangeType.COMMENT_START));
            
            tokens.add(new TypoResults.TokenTracker(curr.endLine, 
                                                    curr.end, 
                                                    TypoResults.RangeType.COMMENT_END));
        }
        
        //sort them according to their start positions
        Collections.sort(tokens, new RangeSorter());
        
        //create the dialog and set the highlighters
        for (int blankInfoRow : blankInfoRows)
            resetDialogsAndCreateHighlighter(editor, table, blankInfoRow);
        
        //start populating the fields accordingly
        TypoResults.RangeType prev = null;
        int blanks = 0;
                
        for (int i = 0; i < tokens.size(); i++)
        {
            TypoResults.TokenTracker curr = tokens.get(i);
            if (curr.rangeType == TypoResults.RangeType.BLANK)
            {
                if (prev == null)
                    blanks++;
                else
                {
                    TypoResults.TokenTracker prevToken = tokens.get(i-1);
                    if (prevToken.rangeType == TypoResults.RangeType.BLANK &&
                            prevToken.startLine == curr.startLine - 1)
                        blanks++;
                    else
                        blanks = 1;
                }
            }
            
            else
            {
                int line = curr.startLine;
                
                if (prev == TypoResults.RangeType.HEADER_END)
                    setBlankDialog(table, editor, TypoMainTable.AFTER_HEADER, blanks, line);
                
                switch (curr.rangeType)
                {
                    case TYPE_DECL_START:
                        if (prev == TypoResults.RangeType.TYPE_DECL_END)
                            setBlankDialog(table, editor, 
                                    TypoMainTable.BTW_CLASSES,
                                    blanks, line);
                        else if (prev == TypoResults.RangeType.COMMENT_END)
                        {
                            TypoDetailsDialog currDL = table.getDialog(TypoMainTable.BEFORE_COMMENTS);
                            blanks = Integer.parseInt(currDL.getValueAt(currDL.getRowCount() - 1, 1));
                            setBlankDialog(table, editor, 
                                    TypoMainTable.BTW_CLASSES,
                                    blanks, line);
                        }
                        break;
                        
                    case METHOD_START:
                        if (prev == TypoResults.RangeType.METHOD_END)
                            setBlankDialog(table, editor, 
                                    TypoMainTable.BTW_METHODS,
                                    blanks, line);
                        
                        else if (prev == TypoResults.RangeType.FIELD_END)
                            setBlankDialog(table, editor, 
                                    TypoMainTable.BTW_FIELDS_METHOD,
                                    blanks, line);
                        break;
                        
                    case COMMENT_START:
                        setBlankDialog(table, editor, TypoMainTable.BEFORE_COMMENTS,
                                blanks, line);
                }
                blanks = 0;
                prev = curr.rangeType;
            }
        }
        
        //calculate averages to put in the main table
        for (int blankInfoRow : blankInfoRows)
        {
            TypoDetailsDialog dialog = table.getDialog(blankInfoRow);
            
            if (dialog.getRowCount() > 0)
            {
                int sum = 0;
                int total = dialog.getRowCount();
                for (int i = 0; i < total; i++)
                    sum += Integer.parseInt(dialog.getValueAt(i, 1));
                table.setValueAt(Misc.DECIMAL_FORMAT.format((sum + 0.0)/ total), blankInfoRow, 
                        TypoMainTable.RESULTS_COLUMN);
            }
            
            this.setBlankStandard(table, blankInfoRow, 
                    (String)table.getValueAt(blankInfoRow, TypoMainTable.RESULTS_COLUMN));
        }
    }
    
    /**
     * Sorts the tokens. Only compares the start positions 
     * as the token positions will not overlap.
     */
    private class RangeSorter implements Comparator<TypoResults.TokenTracker>
    {
        @Override
        public int compare(TypoResults.TokenTracker o1, TypoResults.TokenTracker o2) {
            return o1.pos - o2.pos;
        }
        
    }
    
    public void updateBraceStyle(TypoResults tokenResults, JTextComponent editor,
            TypoMainTable table)
    {
        TypoDetailsDialog dialog = table.getDialog(TypoMainTable.BRACE_STYLE);
        dialog.reset();
        KeyHighlighter highlighter = new KeyHighlighter(editor);
        
        List<BraceInfo> list = tokenResults.getBraces();
        HashSet<TypoResults.BraceStyle> styles = new 
                HashSet<TypoResults.BraceStyle>(TypoResults.BraceStyle.values().length);
        
        for (BraceInfo item : list)
        {
            dialog.addRow(String.valueOf(item.tokPosWithLine.startLine), item.braceStyle.toString());
            KeyHighlighter.addKeyHighlightPos(highlighter, item.tokPosWithLine.start, item.tokPosWithLine.start + 1);
            KeyHighlighter.addAuxHighlight(highlighter, item.tokPosWithLine.end, item.tokPosWithLine.end + 1);
            styles.add(item.braceStyle);
        }
        
        //adds all the styles encountered to the main table
        StringBuilder result = new StringBuilder("");
        Iterator<TypoResults.BraceStyle> iter = styles.iterator();
        if (iter.hasNext())
            result.append(iter.next().toString());
        while (iter.hasNext())
            result.append(", " + iter.next().toString());
        
        table.setValueAt(styles.isEmpty() ? "-" : result.toString(), 
                    TypoMainTable.BRACE_STYLE, TypoMainTable.RESULTS_COLUMN);
        
        table.setValueAt(styles.size() == 1 && !styles.contains(TypoResults.BraceStyle.UNKNOWN) ? 
                JavaStandards.getBestComment() : "-", TypoMainTable.BRACE_STYLE, 
                TypoMainTable.COMMENTS_COLUMN);
        
        dialog.setHighlighter(highlighter);
        dialog.setVisible(false);
    }
    
    public void updateStatementsPerLine(TypoResults tokenResults, JTextComponent editor,
            TypoMainTable table)
    {
        TypoDetailsDialog dialog = table.getDialog(TypoMainTable.STMT_PER_LINE);
        dialog.reset();
        KeyHighlighter highlighter = new KeyHighlighter(editor);
        
        int maximum = 0;
        int lines = 1;
        List<Integer> stmtLines = tokenResults.getStmtLines(); 
        
        for (int i = 0; i < stmtLines.size(); i++)
        {
            int line = stmtLines.get(i);
            if (i > 0 && line == stmtLines.get(i-1))
                lines++;
            else
            {
                if (i > 0)
                {
                    int prevLine = stmtLines.get(i-1);
                    KeyHighlighter.addKeyHighlight(highlighter, editor, "", prevLine, 0);
                    dialog.addRow(String.valueOf(prevLine), String.valueOf(lines));
                }
                
                maximum = Math.max(maximum, lines);
                lines = 1;
            }
        }
        int lastLine = -1;
        if (stmtLines.size() > 1 && (lastLine = stmtLines.get(stmtLines.size() - 1)) == 
                                    stmtLines.get(stmtLines.size() - 2))
        {
            maximum = Math.max(maximum, lines);
            KeyHighlighter.addKeyHighlight(highlighter, editor, "", lastLine, 0);
            dialog.addRow(String.valueOf(lastLine), String.valueOf(lines));
        }
        
        table.setValueAt(String.valueOf(maximum), TypoMainTable.STMT_PER_LINE, 
                TypoMainTable.RESULTS_COLUMN);
        
        table.setValueAt(maximum == Integer.parseInt((String)table.getValueAt(TypoMainTable.STMT_PER_LINE, 
                TypoMainTable.STANDARDS_COLUMN)) ? JavaStandards.getBestComment() : "-", 
                TypoMainTable.STMT_PER_LINE, TypoMainTable.COMMENTS_COLUMN);
        
        dialog.setHighlighter(highlighter);
        dialog.setVisible(false);
    }
    
    /**
     * Updates the percentage of blank lines and comments.
     * @param tokenResults
     * @param editor
     * @param table 
     */
    public void updatePercentages(TypoResults tokenResults, JTextComponent editor,
            TypoMainTable table, List<TokenPosWithLine> blanks, List<TokenPosWithLine> comments, int[] info)
    {   
        //process the percentage of blanks
        TypoDetailsDialog blanksDialog = table.getDialog(TypoMainTable.BLANKS);
        blanksDialog.reset();
        
        KeyHighlighter blanksHighlighter = new KeyHighlighter(editor);
        
        for (TokenPosWithLine pos : blanks)
        {
            blanksDialog.addRow(String.valueOf(pos.startLine));
            KeyHighlighter.addKeyHighlight(blanksHighlighter, editor, 
                        "", pos.startLine, 0);
        }
        
        table.setValueAt(info[1] == 0 ? "-" : String.format("%.2f", info[0] * 100.0 / info[1]), 
                        TypoMainTable.BLANKS, 
                        TypoMainTable.RESULTS_COLUMN);
        
        table.setValueAt("-", 
                            TypoMainTable.BLANKS, 
                            TypoMainTable.COMMENTS_COLUMN);
        
        blanksDialog.setHighlighter(blanksHighlighter);
        blanksDialog.setVisible(false);
        
        //process percentage of comments
        TypoDetailsDialog commentsDialog = table.getDialog(TypoMainTable.COMMENTS);
        commentsDialog.reset();
        
        KeyHighlighter commentsHighlighter = new KeyHighlighter(editor);
        
        int commentLineCount = 0;        
        for (TokenPosWithLine pos : comments)
        {
            int start = pos.startLine;
            int end = pos.endLine;
            if (start == end)
                commentsDialog.addRow(String.valueOf(pos.startLine));
            else
                commentsDialog.addRow(String.valueOf(pos.startLine) + " to " + 
                        String.valueOf(pos.endLine));
            commentLineCount += end - start + 1;
            KeyHighlighter.addKeyHighlightPos(commentsHighlighter, pos.start, pos.end);
        }
        
        table.setValueAt(info[1] == 0 ? "-" : String.format("%.2f", commentLineCount * 100.0 / info[1]), 
                            TypoMainTable.COMMENTS, 
                            TypoMainTable.RESULTS_COLUMN);
        
        table.setValueAt("-", 
                            TypoMainTable.COMMENTS, 
                            TypoMainTable.COMMENTS_COLUMN);
        
        commentsDialog.setHighlighter(commentsHighlighter);
        commentsDialog.setVisible(false);
    }
    
    /**
     * Finds the statements that has lengths which exceed the given 
     * length. 
     * @param tokenResults
     * @param editor
     * @param table 
     */
    public void updateMaximumLength(TypoResults tokenResults, JTextComponent editor,
            TypoMainTable table)
    {
        TypoDetailsDialog dialog = table.getDialog(TypoMainTable.LONGEST_LEN);
        dialog.reset();
        int longest = 0;
        KeyHighlighter spacesHighlighter = new KeyHighlighter(editor);
        
        List<TokenPosition> list = LexicalUtility.getLineLengths(editor.getText());
        
        for (TokenPosition pos : list)
        {
            dialog.addRow(String.valueOf(pos.start), String.valueOf(pos.end));
            
            KeyHighlighter.addKeyHighlight(spacesHighlighter, editor, 
                        "", pos.start, 0);
            
            if (pos.end > longest)
                longest = pos.end;
        }
        
        table.setValueAt(longest == 0 ? "-" : String.valueOf(longest), TypoMainTable.LONGEST_LEN, 
                TypoMainTable.RESULTS_COLUMN);
        
        table.setValueAt(longest <= Integer.parseInt((String)table.getValueAt(TypoMainTable.LONGEST_LEN, 
                TypoMainTable.STANDARDS_COLUMN)) ? JavaStandards.getBestComment() : "-", 
                TypoMainTable.LONGEST_LEN, TypoMainTable.COMMENTS_COLUMN);
        
        dialog.setHighlighter(spacesHighlighter);
        dialog.setVisible(false);
    }
    
    /**
     * Updating the average lengths of method and class.
     * @param tokenResults
     * @param editor
     * @param table 
     */
    private void updateAverageLengths(TypoResults tokenResults, JTextComponent editor, TypoMainTable table)
    {
        int[] rows = { TypoMainTable.AVG_LEN_METHOD, TypoMainTable.AVG_LEN_CLASS };
        double avg = 0;
        
        for (int row : rows)
        {
            TypoDetailsDialog dialog = table.getDialog(row);
            dialog.reset();
            
            KeyHighlighter spacesHighlighter = new KeyHighlighter(editor);
            
            HashMap<String, TokenPosition> list = null;
            switch (row)
            {
                case TypoMainTable.AVG_LEN_METHOD:
                    list = tokenResults.getMethodLengths();
                    avg = tokenResults.getAverageMethodLength();
                    break;
                    
                case TypoMainTable.AVG_LEN_CLASS:
                    list = tokenResults.getClassLengths();
                    avg = tokenResults.getAverageClassLength();
                    break;
            }
                        
            table.setValueAt(String.format("%.2f", avg), row, 
                            TypoMainTable.RESULTS_COLUMN);
            
            table.setValueAt(avg <= Integer.parseInt((String)table.getValueAt(row, TypoMainTable.STANDARDS_COLUMN)) ? 
                    JavaStandards.getBestComment() : "-", row, TypoMainTable.COMMENTS_COLUMN);
            
            List<Integer> lengths = new ArrayList<Integer>();
            
            for (String name : list.keySet())
            {
                TokenPosition curr = list.get(name);
                dialog.addRow(name, String.valueOf(curr.start), 
                        String.valueOf(curr.end - curr.start + 1));
                KeyHighlighter.addKeyHighlight(spacesHighlighter, editor, 
                        "", curr.start, 0);
                
                try {
                    lengths.add(ActionUtils.getLineNumber(editor, curr.end) - 
                            ActionUtils.getLineNumber(editor, curr.start));
                } catch (BadLocationException e) {
                    e.printStackTrace();
                }
            }
            table.setValueAt(String.format("%.2f", JavaStandards.getStandardDeviation(lengths)), 
                    row, TypoMainTable.SD_COLUMN);
                        
            dialog.setHighlighter(spacesHighlighter);
            dialog.setVisible(false);
        }
    }
    
    /**
     * Processes the number of single identifiers found and updates the
     * appropriate dialog.
     * @param tokenResults
     * @param editor
     * @param table 
     */
    private void updateSingleVariables(TypoResults tokenResults, JTextComponent editor, TypoMainTable table)
    {
        TypoDetailsDialog dialog = table.getDialog(TypoMainTable.SINGLE_VARIABLES);
        dialog.reset();
        
        KeyHighlighter highlighter = new KeyHighlighter(editor);
        
        int count = 0;
        
        for (KeyRecord rec : tokenResults.getVariableNames())
            if (rec.text.length() == 1)
            {
                dialog.addRow(rec.text, String.valueOf(rec.line), String.valueOf(rec.column));
                KeyHighlighter.addKeyHighlightPos(highlighter, rec.pos, rec.pos + 1);
                count++;
            }
        
        table.setValueAt(String.valueOf(count), TypoMainTable.SINGLE_VARIABLES, TypoMainTable.RESULTS_COLUMN);
        table.setValueAt(JavaStandards.getSingleCharacterIdComment(count, 
                Integer.parseInt((String)table.getValueAt(TypoMainTable.SINGLE_VARIABLES, TypoMainTable.STANDARDS_COLUMN))),
                TypoMainTable.SINGLE_VARIABLES, TypoMainTable.COMMENTS_COLUMN);
        
        dialog.setHighlighter(highlighter);
            dialog.setVisible(false);
    }
    
    /**
     * Updates the fields of variable / constants naming, method naming 
     * and class or interface naming.
     * @param tokenResults
     * @param editor
     * @param table 
     */
    private void updateIdentifiers(TypoResults tokenResults, JTextComponent editor, TypoMainTable table)
    {
        for (int i : identifierRows)
        {
            TypoDetailsDialog dialog = table.getDialog(i);
            dialog.reset();
            
            KeyHighlighter spacesHighlighter = new KeyHighlighter(editor);
            
            List<KeyRecord> list = null;
            switch (i)
            {
                case TypoMainTable.VARIABLE_NAME:
                    list = tokenResults.getVariableNames();
                    break;
                    
                case TypoMainTable.METHOD_NAME:
                    list = tokenResults.getMethodNames();
                    break;
                    
                case TypoMainTable.CLASS_INTERFACE_NAME:
                    list = tokenResults.getClassInterfaceNames();
                    break;
                    
                case TypoMainTable.CONSTANT_NAME:
                    list = tokenResults.getConstantNames();
                    break;
            }
            
            if (list.isEmpty())
            {
                table.setValueAt("-", i, TypoMainTable.RESULTS_COLUMN);
                table.setValueAt("-", i, TypoMainTable.COMMENTS_COLUMN);
            }
            
            //add every style encountered as part of the result
            else
            {
                JavaStandards.NamingStyle[] styles = new JavaStandards.NamingStyle[JavaStandards.NamingStyle.values().length];
                int size = 1;
                
                KeyRecord first = list.get(0);
                styles[0] = JavaStandards.getNamingStyle(first.text);
                String result = styles[0].toString();
                                
                dialog.addRow(first.text, String.valueOf(first.line), 
                        String.valueOf(first.column), result);
                
                KeyHighlighter.addKeyHighlightPos(spacesHighlighter, first.pos,
                        first.pos + first.text.length());
                
                for (int j = 1; j < list.size(); j++)
                {
                    KeyRecord curr = list.get(j);
                    
                    JavaStandards.NamingStyle currStyle = 
                            JavaStandards.getNamingStyle(curr.text);
                    
                    //ensure uniqueness of style before its added to result
                    boolean encountered = false;
                    for (int k = 0; k < size; k++)
                    {
                        if (currStyle == styles[k])
                        {
                            encountered = true;
                            break;
                        }
                    }
                    
                    if (!encountered)
                    {
                        result += ", " + currStyle.toString();
                        styles[size++] = currStyle;
                    }
                    
                    dialog.addRow(curr.text, String.valueOf(curr.line), 
                            String.valueOf(curr.column), currStyle.toString());
                    
                    KeyHighlighter.addKeyHighlightPos(spacesHighlighter, curr.pos,
                            curr.pos + curr.text.length());
                }
                
                table.setValueAt(result, i, TypoMainTable.RESULTS_COLUMN);
                
                //set the comments
                table.setValueAt(JavaStandards.getIdentifierNamingComment(result, 
                    table.getValueAt(i, TypoMainTable.STANDARDS_COLUMN).toString()), 
                        i, TypoMainTable.COMMENTS_COLUMN);
            }
            
            dialog.setHighlighter(spacesHighlighter);
            dialog.setVisible(false);
        }
    }    
    
    private void updateUnary(TypoResults tokenResults, JTextComponent editor, TypoMainTable table) throws 
            org.antlr.runtime.RecognitionException, BadLocationException
    {
        TypoDetailsDialog dialog = table.getDialog(TypoMainTable.UNARY_OPERATORS);
        dialog.reset();
        
        ArrayList<Integer> spaces = new ArrayList<Integer>();
        
        KeyHighlighter spacesHighlighter = new KeyHighlighter(editor);
        
        SpaceProperty spaceProp = null;
        String[][] arrs = { unaryPrefix, unaryPostfix };
        
        for (int i = 0; i < arrs.length; i++)
        {
            for (String key : arrs[i])
            {
                if ((spaceProp = tokenResults.get(key)) != null)
                {                
                    for (KeyRecord rec : (i == 0) ? spaceProp.getSpaceAfter() : spaceProp.getSpaceBefore())
                    {                    
                        dialog.addRow(Key.decode(key) + ((i == 0) ? " (prefix)" : " (postfix)"), 
                                String.valueOf(rec.line), 
                                String.valueOf(rec.column + 1),
                                String.valueOf(rec.spaces)
                                );

                        KeyHighlighter.addKeyHighlightPos(spacesHighlighter, rec.pos, 
                                rec.pos + Key.decode(key).length());

                        spaces.add(rec.spaces);
                    }
                }
            }
        }
        
        dialog.setHighlighter(spacesHighlighter);
        dialog.setVisible(false);
        
        setSpaceDisplay(table, spaces, TypoMainTable.UNARY_OPERATORS);
    }
    
    /**
     * Update spaces that occur before a keyword.
     * @param tokenResults
     * @param editor
     * @param table
     * @throws org.antlr.runtime.RecognitionException
     * @throws BadLocationException 
     */
    private void updateSpaceBefore(TypoResults tokenResults, JTextComponent editor, TypoMainTable table) throws 
            org.antlr.runtime.RecognitionException, BadLocationException
    {
        for (int i = 0; i < spacesBeforeRows.length; i++)
        {
            List<Integer> spacesBefore = processSpaceBefore(table.getDialog(spacesBeforeRows[i]),  
                tokenResults, editor, spacesBeforeKeys[i]);
            setSpaceDisplay(table, spacesBefore, spacesBeforeRows[i]);
        }
    }
    
    /**
     * Sets the appropriate table entries for space after.
     * @param tokenResults
     * @param editor
     * @param table
     * @throws org.antlr.runtime.RecognitionException
     * @throws BadLocationException 
     */
    private void updateSpaceAfter(TypoResults tokenResults, JTextComponent editor, TypoMainTable table) throws 
            org.antlr.runtime.RecognitionException, BadLocationException
    {
        for (int i = 0; i < spacesAfterRows.length; i++)
        {
            List<Integer> spacesAfter = processSpaceAfter(table.getDialog(spacesAfterRows[i]),  
                tokenResults, editor, spacesAfterKeys[i]);
            setSpaceDisplay(table, spacesAfter, spacesAfterRows[i]);
        }
    }
    
    /**
     * Displays the spaces before an after an identifier
     * @param tokenResults
     * @param editor
     * @param table
     * @throws org.antlr.runtime.RecognitionException
     * @throws BadLocationException 
     */
    private void updateSpaceBeforeAndAfter(TypoResults tokenResults, JTextComponent editor, TypoMainTable table) throws 
            org.antlr.runtime.RecognitionException, BadLocationException
    {
        for (int i = 0; i < spacesBeforeAfterRows.length; i++)
        {
            List<Integer> spacesBeforeAfter = processSpaceBeforeAndAfter(
                    table.getDialog(spacesBeforeAfterRows[i]), tokenResults, 
                    editor, spacesBeforeAfterKeys[i]);
            
            setSpaceDisplay(table, spacesBeforeAfter, spacesBeforeAfterRows[i]);
        }
    }
    
    /**
    * Sets the spaces before/after a keyword at a specific row.
    * @param spacesAfter
    * @param table
    * @param row 
    */
    public void setSpaceDisplay(TypoMainTable table, List<Integer> spaces, int row)
    {
        double standardDeviation = JavaStandards.getStandardDeviation(spaces);
        
        table.setValueAt(spaces.isEmpty() ? "-" : Misc.DECIMAL_FORMAT.format(getAverage(spaces)), 
                row, 
                TypoMainTable.RESULTS_COLUMN);
        
        table.setValueAt(spaces.isEmpty() ? "-" : Misc.DECIMAL_FORMAT.format(standardDeviation), row,
                TypoMainTable.SD_COLUMN);
        
        table.setValueAt(spaces.isEmpty() ? "-" : 
                JavaStandards.getStandard(standardDeviation), row, 
                TypoMainTable.COMMENTS_COLUMN);
    }
    
    private double getAverage(List<Integer> nums)
    {
        if (nums.isEmpty())
            return 0;
        
        double result = 0;
        
        for (int a : nums)
            result += a;
        
        result /= nums.size();
        
        return result;
    }
    
    /**
     * Calculates the average number of horizontal spaces before a keyword.
     * Updates the fields dialog at the same time.
     * @param dialog
     * @param tokenResults
     * @param editor
     * @param keywords
     * @return average number of spaces, or -1 if there are no keywords to process.
     * @throws BadLocationException 
     */
    private List<Integer> processSpaceBefore(TypoDetailsDialog dialog, 
            TypoResults tokenResults, JTextComponent editor, String... keywords) 
            throws BadLocationException
    {
        return processSpace(false, dialog, tokenResults, editor, keywords);
    }
    
    /**
     * Calculates the average number of horizontal spaces after a keyword.
     * Updates the fields dialog at the same time.
     * @param dialog 
     * @param tokenResults
     * @param editor
     * @param keywords
     * @return average number of spaces, or -1 if there are no keywords to process.
     * @throws BadLocationException 
     */
    private List<Integer> processSpaceAfter(TypoDetailsDialog dialog, 
            TypoResults tokenResults, JTextComponent editor, String... keywords) 
            throws BadLocationException
    {
        return processSpace(true, dialog, tokenResults, editor, keywords);
    }
    
    /**
     * Displays the space before and after an identifier
     * @param dialog
     * @param tokenResults
     * @param editor
     * @param keywords
     * @return
     * @throws BadLocationException 
     */
    private List<Integer> processSpaceBeforeAndAfter(TypoDetailsDialog dialog, 
            TypoResults tokenResults, JTextComponent editor, String... keywords) 
            throws BadLocationException
    {
        dialog.reset();
        
        ArrayList<Integer> spaces = new ArrayList<Integer>();
        
        KeyHighlighter spacesHighlighter = new KeyHighlighter(editor);
        
        SpaceProperty spaceProp = null;
        for (String key : keywords)
        {
            if ((spaceProp = tokenResults.get(key)) != null)
            {
                List<KeyRecord> propBefore = spaceProp.getSpaceBefore();
                List<KeyRecord> propAfter = spaceProp.getSpaceAfter();
                
                if (propBefore.size() != propAfter.size())
                    throw new RuntimeException("Number of elements in space before" +
                            "does not match number of elements in space after.");
                
                for (int i = 0; i < propBefore.size(); i++)
                {
                    KeyRecord beforeRec = propBefore.get(i);
                    KeyRecord afterRec = propAfter.get(i);
                    
                    
                    dialog.addRow(Key.decode(key), 
                            String.valueOf(beforeRec.line), 
                            String.valueOf(afterRec.column + 1),
                            String.valueOf(beforeRec.spaces),
                            String.valueOf(afterRec.spaces)
                            );
                    
                    KeyHighlighter.addKeyHighlightPos(spacesHighlighter, beforeRec.pos, 
                            beforeRec.pos + Key.decode(key).length());
                    
                    spaces.add(beforeRec.spaces); 
                    spaces.add(afterRec.spaces);
                }
            }
        }
        
        dialog.setHighlighter(spacesHighlighter);
        dialog.setVisible(false);
        
        return spaces;    
    }
    
    /**
     * Calculates the average number of horizontal spaces before/after a keyword.
     * Updates the fields dialog at the same time.
     * @param after - true to set to update spaces after keyword, false to update spaces before.
     * @param dialog 
     * @param tokenResults
     * @param editor
     * @param keywords
     * @return average number of spaces, or -1 if there are no keywords to process.
     * @throws BadLocationException 
     */
    private List<Integer> processSpace(boolean after, TypoDetailsDialog dialog, 
            TypoResults tokenResults, JTextComponent editor, String... keywords) 
            throws BadLocationException
    {
        dialog.reset();
        
        ArrayList<Integer> spaces = new ArrayList<Integer>();
        
        KeyHighlighter spacesHighlighter = new KeyHighlighter(editor);
        
        SpaceProperty spaceProp = null;
        for (String key : keywords)
        {
            if ((spaceProp = tokenResults.get(key)) != null)
            {
                List<KeyRecord> prop = after ? spaceProp.getSpaceAfter() : 
                                            spaceProp.getSpaceBefore();
                for (KeyRecord rec : prop)
                {
                    dialog.addRow(Key.decode(key), 
                            String.valueOf(rec.line), 
                            String.valueOf(rec.column + 1),
                            String.valueOf(rec.spaces));
                    
                    KeyHighlighter.addKeyHighlightPos(spacesHighlighter, rec.pos, 
                            rec.pos + Key.decode(key).length());
                    
                    spaces.add(rec.spaces);
                }
            }
        }
        
        dialog.setHighlighter(spacesHighlighter);
        dialog.setVisible(false);
        
        return spaces;
    }
}
